#include <qstringlist.h>
#include <qmimedata.h>

#include "LightPresetModel.h"
#include "LightPresetData.h"
#include "LightPresetFactory.h"
#include "modelViewRules.h"
#include "testableAssert.h"


#define DEFAULT_PRESET_NAME    QString("new preset")
#define DEFAULT_TRNSITION_TIME    1000


LightPresetModel::LightPresetModel(int numberOfChannels, LightPresetFactory *presetFactory,
                                   QObject *parent) :
   QAbstractListModel(parent),
   m_presetFactory(presetFactory),
   m_numberOfChannels(numberOfChannels)
{
   T_ASSERT( m_presetFactory != NULL);
   m_presetDataStore.clear();
}

LightPresetModel::~LightPresetModel()
{
   delete m_presetFactory;
}

/**
 * @return retrive the number of stored preset
 */
int LightPresetModel::rowCount(const QModelIndex &) const
{
   return m_presetDataStore.length();
}

/**
 * @brief generic getter for an item of the model
 * @param index - identifies the desired preset
 * @param role - identifies what information is desired from preset at \p index
 * @return the desired info
 */
QVariant LightPresetModel::data(const QModelIndex &index, int role) const
{
   QVariant data = QVariant();

   int position = index.row();
   assertValidPosition( position);

   const LightPresetData *preset = m_presetDataStore.at(position);

   switch (role)
   {
   case Qt::DisplayRole:
      data = QVariant( preset->name() );
      break;
   case modelViewRules::Binary:
      data = QVariant::fromValue( preset );
      break;
   case modelViewRules::StringId:
      data = QVariant(preset->name());
      break;
   default:
      break;
   }

   return data;
}

bool LightPresetModel::removeRows(int row, int count, const QModelIndex &parent)
{
   T_ASSERT( row >= 0);
   T_ASSERT( (row + count) <= rowCount() );

   if (count > 0)
   {
      beginRemoveRows( parent, row, row + count - 1);

      for (int i = 0; i < count; i++)
      {
         /* each 'remove' operation shitfs next element */
         m_presetDataStore.removeAt( row);
      }

      emit layoutChanged();
      endRemoveRows();
   }

   return true;
}

QVariant LightPresetModel::headerData(int /*section*/, Qt::Orientation /*orientation*/,
                                      int /*role*/) const
{
   return QVariant();
}

/**
 * create a preset with a default name and lightset values to 0.
 * New preset should be renamed before calling this funciton again,
 * otherwise old preset would modified, instead of creating a new one.
 */
void LightPresetModel::createVoidPreset()
{
   QList<double> lightValues;
   for (int i=0; i < m_numberOfChannels; i++)
   {
      lightValues << 0.0;
   }

   updatePreset( DEFAULT_PRESET_NAME, DEFAULT_TRNSITION_TIME, lightValues);
}

/**
 * @brief add the preset passed as parameter in the model, if a
 *        preset with same name is not already present.
 *  If a preset with same 'name' is present, it is updated with
 *  passed \p preset parameters.
 * @param preset
 *
 * @note This function is equivalent to setData in canonical Model/View paqradigm
 */
void LightPresetModel::updatePreset(const LightPresetData &preset)
{
   T_ASSERT( preset.levelList().size() == m_numberOfChannels);

   /* search a preset with same name of passed one */
   QModelIndex indexOfExistingPreset = searchByName(preset.name());

   if (indexOfExistingPreset != QModelIndex())
   {
      /* delete preset with same name before adding the new,
       * so it seems that one preset is updated */
      m_presetDataStore.replace( indexOfExistingPreset.row(), &preset );
   }
   else
   {
      /* add new preset */
      m_presetDataStore << &preset;
   }

   emit layoutChanged();
}

void LightPresetModel::updatePreset(const QString &name, int fadeMs, const QList<double> &targetValues)
{
   /* ensure there are enough values in preset list */
   QList<double> filledTargetValues = fillTargetValues( targetValues);

   LightPresetData *updatedItem = m_presetFactory->build( name, fadeMs, filledTargetValues, this);

   /* if preset is new, memory is correctly allocated. If it's an update
    * of an existing preset, function below will deallocate old item
    * and keep the newly created. */
   updatePreset( *updatedItem );
}

QList<double> LightPresetModel::fillTargetValues( const QList<double> &targetValues)
{
   QList<double> filledTargetValues = targetValues;

   if (filledTargetValues.count() < m_numberOfChannels)
   {
      for (int i=0; i < (m_numberOfChannels - targetValues.count()); i++)
      {
         filledTargetValues << 0.0;
      }
   }

   return filledTargetValues;
}

/**
 * @brief overload method that uses preset name directly
 * @param presetName - label of preset
 */
void LightPresetModel::removePreset(const QString &presetName)
{
   QModelIndex indexOfExistingPreset = searchByName( presetName );

   if (indexOfExistingPreset != QModelIndex())
   {
      T_ASSERT( indexOfExistingPreset.row() < m_presetDataStore.length());
      m_presetDataStore.removeAt( indexOfExistingPreset.row() );

      emit layoutChanged();
   }
}

/**
 * @brief remove preset given in parameter
 * @param preset
 */
void LightPresetModel::removePreset(const LightPresetData &preset)
{
   int numberOfRemoved = m_presetDataStore.removeAll( &preset);

   if (numberOfRemoved > 0)
   {
      emit layoutChanged();
   }
}

/**
 * @brief if preset with name \p oldName is found, it is replaced with one
 *    with name \p newName. If \p oldName is not found, nothing is done.
 */
void LightPresetModel::renamePreset(const QString &oldName, const QString &newName)
{
   QModelIndex indexOfExistingPreset = searchByName( oldName );

   if (indexOfExistingPreset != QModelIndex())
   {
      T_ASSERT( indexOfExistingPreset.row() < m_presetDataStore.length());

      int row = indexOfExistingPreset.row();
      const LightPresetData *oldItem = m_presetDataStore.at( row );
      LightPresetData *updatedItem;
      updatedItem = new LightPresetData( newName, oldItem->fadeTime(), oldItem->levelList());
      m_presetDataStore.replace( row, updatedItem);

      emit layoutChanged();
   }
}

/**
 * @brief retrive model index for a preset, given the string of its name
 * @param targetName - name of desired preset
 * @return the model index, or QModelIndex() in case of no match
 */
QModelIndex LightPresetModel::searchByName(const QString &targetName) const
{
   int position = -1;
   bool found = false;

   for (int i = 0; (i < m_presetDataStore.length()) && (!found); i++)
   {
      if (m_presetDataStore.at(i)->name() == targetName)
      {
         found = true;
         position = i;
      }
   }

   return index( position);
}

/**
 * @brief base class reimplementation. Source and destination indexes are ignored.
 * @param sourceRow - first row to be moved
 * @param count - number of rows moved from \p sourceRow
 * @param destinationChild - final position of the first row moved
 *
 * @return true if succesfully moved; false otherwise
 */
bool LightPresetModel::moveRows(const QModelIndex &/*sourceParent*/, int sourceRow, int count,
                                const QModelIndex &/*destinationParent*/, int destinationChild)
{
   bool moveDone = false;

   if ((sourceRow >= 0) && (sourceRow < rowCount()) &&
       (destinationChild >= 0) && (destinationChild < rowCount()) &&
       (count > 0) && (count <= rowCount())
      )
   {
      moveRows( sourceRow, count, destinationChild);
      moveDone = true;
   }

   return moveDone;
}

/**
 * @brief overload function that assumes that parameters are consistent
 */
void LightPresetModel::moveRows(int sourceRow, int count, int destinationRow)
{
   for (int i=0; i < count; i++)
   {
      m_presetDataStore.move( sourceRow + i, destinationRow + i );
   }

   /* this avoids need for BeginMoveRow and EndMoveRow */
   layoutChanged();
}

/**
 * @brief if \p position is outside model limits, an exception is rised
 * @param position - same as 'row' for model index
 */
void LightPresetModel::assertValidPosition(int position) const
{
   T_ASSERT_REP ((position >= 0) && (position < m_presetDataStore.length()),
                 QString("LPM: retriving data from invalid index: %1").arg(position));
}


Qt::ItemFlags LightPresetModel::flags(const QModelIndex & index) const
{
   Qt::ItemFlags flags = QAbstractListModel::flags(index);

   if (index.isValid())
   {
      flags |= Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
   }

   return  flags;
}
